package tinyinventor.compiler;

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import codeblocks.Block;

public class NescCompiler extends Compiler {

	public static final String CONFIGURATION_NAME = "TinyInventorC";
	public static final String MODULE_NAME = "TinyInventorP";
	public static int STACK_SIZE = 200;

	// Prototype components loaded from XML file
	private static Map<String,NescComponent> prototypeComponents = new HashMap<String, NescComponent>();
	
	protected Map<Long, NescComponent> blockComponents;
	protected Map<String,NescComponent> singletonComponents;
	
	boolean addPrintf = true;
	boolean addSensors = true;
	
	public NescCompiler() {
		blockComponents = new HashMap<Long,NescComponent>();
		singletonComponents = new HashMap<String,NescComponent>();
	}
		
	private Collection<NescComponent> getComponents() {
		LinkedList<NescComponent> components = new LinkedList<NescComponent>();
		components.addAll(blockComponents.values());
		components.addAll(singletonComponents.values());
		return components;
	}
	
	@Override
	public Component getComponent(Block block) throws CompileException {
		if(blockComponents.containsKey(block.getBlockID())) {
			return blockComponents.get(block.getBlockID());
		} else {
			NescComponent c = getPrototypeComponent(block.getGenusName()).clone();
			blockComponents.put(block.getBlockID(), c);
			if(!c.getUsesSingleton().equals("") && !singletonComponents.containsKey(c.getUsesSingleton())) {
				singletonComponents.put(c.getUsesSingleton(), getPrototypeComponent(c.getUsesSingleton()).clone());
			}
			return c;
		}
	}
	
	/**
	 * Write the current compiled program to files under path
	 * @param path the path were the files are to be written too
	 * @throws IOException
	 */
	public void build(String path) throws IOException {
		super.build(path);
		
		File configurationFile = new File(path + "/" + CONFIGURATION_NAME + ".nc");
		File moduleFile = new File(path + "/" + MODULE_NAME + ".nc");
		File makeFile = new File(path + "/Makefile");
		StringBuilder code;
		BufferedWriter writer;
		
		code = new StringBuilder();
		code.append(DECLARATION_STRING + "\n\n");
		code.append("configuration " + CONFIGURATION_NAME + " {\n");
		code.append("} implementation {\n\n");
		code.append("components "+MODULE_NAME+";\n");
		code.append("components MainC as InitMainC;\n");
		code.append("InitMainC.SoftwareInit ->"+MODULE_NAME+";\n\n");
		code.append(getWiringCode());
		if(addPrintf) {
			code.append("components PrintfC;\n\n");
		}
		code.append("}\n");
		writer = new BufferedWriter(new FileWriter(configurationFile));
		writer.write(code.toString());
		writer.close();
		indent(configurationFile);
		
		code = new StringBuilder();
		code.append(DECLARATION_STRING + "\n\n");
		if(addPrintf) {
			code.append("#include <printf.h>\n");
		}
		code.append("#include <message.h>\n\n");
		code.append("#include <Udp.h>\n\n");
		code.append("#include <UserButton.h>\n\n");
		code.append("module " + MODULE_NAME + " {\n\n");
		code.append("provides interface Init;\n");
		code.append(getInterfaceCode());
		code.append("\n} implementation { \n\n");
		code.append("/* CODE DECLARATIONS */ \n\n");
		code.append(getDeclarationCode());
		code.append("/* CODE IMPLEMENTATIONS */ \n\n");
		code.append(getModuleCode());
		code.append("command error_t Init.init() {\n");
		code.append(getInitCode());
		code.append("return SUCCESS;\n");
		code.append("}\n\n");
		code.append("}\n");
		writer = new BufferedWriter(new FileWriter(moduleFile));
		writer.write(code.toString());
		writer.close();
		indent(moduleFile);
		
		code = new StringBuilder();
		code.append("COMPONENT="+CONFIGURATION_NAME+"\n");
		code.append("CFLAGS += -DCC2420_DEF_CHANNEL=15\n");
		//code.append("CFLAGS += -DIEEE154FRAMES_ENABLED\n");
		if (addPrintf)
			code.append("CFLAGS += -I${TOSDIR}/lib/tosthreads/lib/printf\n");
		if (addSensors)
			code.append("CFLAGS += -I$(TOSDIR)/lib/tosthreads/sensorboards/tmote_onboard\n");
		code.append("CFLAGS += -I${TOSDIR}/lib/tosthreads/lib/net/blip\n");
		code.append("include $(MAKERULES)\n");
		writer = new BufferedWriter(new FileWriter(makeFile));
		writer.write(code.toString());
		writer.close();
	}

	
	
	/**
	 * Get the compilers current components nesc wiring code
	 * @return the code
	 */
	private String getWiringCode() {
		StringBuilder result = new StringBuilder();
		String code;
		for(NescComponent c : blockComponents.values()) {
			code = c.getWiringCode();
			if(!code.equals("")) {
				result.append(code);
				result.append("\n\n");
			}
		}
		for(NescComponent c : singletonComponents.values()) {
			code = c.getWiringCode();
			if(!code.equals("")) {
				result.append(code);
				result.append("\n\n");
			}
		}
		return result.toString();
	}

	/**
	 * Get the compilers current components nesc interface code
	 * @return the code
	 */
	private String getInterfaceCode() {
		StringBuilder result = new StringBuilder();
		String code;
		for(NescComponent c : getComponents()) {
			code = c.getInterfaceCode();
			if(!code.equals("")) {
				result.append(code);
				result.append("\n");
			}
		}
		return result.toString();
	}
	
	/**
	 * Get the compilers current components nesc init code
	 * @return the code
	 */
	private String getInitCode() {
		StringBuilder result = new StringBuilder();
		String code;
		for(NescComponent c : getComponents()) {
			code = c.getInitCode();
			if(!code.equals("")) {
				result.append(code);
				result.append("\n");
			}
		}
		return result.toString();
	}
	
	/**
	 * Get the compilers current components nesc declaration code
	 * @return the code
	 */
	private String getDeclarationCode() {
		StringBuilder result = new StringBuilder();
		
		for(NescComponent c : getComponents()) {
			//System.out.println("adding " + c.getName());
			String code = c.getDeclarationCode();
			if(!code.equals("")) {
				result.append(code);
				result.append("\n\n");
			}
		}
		return result.toString();
	}

	/**
	 * Get the compilers current components nesc module code
	 * @return the code
	 */
	private String getModuleCode() {
		StringBuilder result = new StringBuilder();
		
		for(NescComponent c : getComponents()) {
			//System.out.println("adding " + c.getName());
			String code = c.getModuleCode();
			if(!code.equals("")) {
				result.append(code);
				result.append("\n\n");
			}
		}
		return result.toString();
	}

	/**
	 * Gets a nesc prototype component
	 * @param name the name of the component
	 * @return the component
	 * @throws CompileException if the prototype component cannot be found
	 */
	private static NescComponent getPrototypeComponent(String name) throws CompileException {
		if(prototypeComponents.containsKey(name)) {
			return prototypeComponents.get(name);
		} else {
			throw new CompileException("Block "+name+" is not a valid NesC block.");
		}
	}

	
	/**
	 * Gets nesc prototype components from a components declaration file
	 * @param path the path to the components declaration
	 */
	public static void loadComponentsFromPath(String path) {
		DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
		DocumentBuilder builder;
		Document doc;
		Element element;
		try {
			builder = factory.newDocumentBuilder();
			doc = builder.parse(new File(path));
			element = doc.getDocumentElement();
			
			NodeList componentsRoot = element.getElementsByTagName("Component");
			if(componentsRoot==null) {
				System.err.println("No components found in " + path);
			} else {
				
				for(int i=0; i<componentsRoot.getLength(); i++) {
					Node component = componentsRoot.item(i);
					
					System.out.println(component);
					
					if(!component.getNodeName().equals("Component")) {
						System.err.println("Not a component");
						continue;
					}
				
					NamedNodeMap attr = component.getAttributes();
					if(attr==null) continue;
					Node nameNode = attr.getNamedItem("name");
					if(nameNode==null) continue;
					String name = nameNode.getTextContent();
					
					String usesSingleton = "";
					Node usesSingletonNode = attr.getNamedItem("uses-singleton");
					if(usesSingletonNode!=null) {
						 usesSingleton = usesSingletonNode.getTextContent();
					}
					
					boolean hasVariable = false; 
					Node hasVariableNode = attr.getNamedItem("has-variable");
					if(hasVariableNode!=null) {
						 hasVariable = hasVariableNode.getTextContent().equals("yes");
					}

					String rawCode = "";
					String rawWiringCode = "";
					String rawInterfaceCode = "";
					String rawInitCode = "";
					String rawDeclarationCode = "";
					String rawModuleCode = "";
					
					if(component.hasChildNodes()) {
						Node child = component.getFirstChild();
						while(child!=null) {
							if(child.getNodeName().equals("Code")) {
								rawCode = child.getTextContent().trim();
							} else if(child.getNodeName().equals("WiringCode")) {
								rawWiringCode = child.getTextContent().trim();
							} else if(child.getNodeName().equals("InterfaceCode")) {
								rawInterfaceCode = child.getTextContent().trim();
							} else if(child.getNodeName().equals("InitCode")) {
								rawInitCode = child.getTextContent().trim();
							} else if(child.getNodeName().equals("DeclarationCode")) {
								rawDeclarationCode = child.getTextContent().trim();
							} else if(child.getNodeName().equals("ModuleCode")) {
								rawModuleCode = child.getTextContent().trim();
							}
							
							child = child.getNextSibling();
						}
					}
					
					NescComponent prototypeComponent = new NescComponent(name, usesSingleton, hasVariable, rawCode, rawWiringCode, rawInterfaceCode, rawInitCode, rawDeclarationCode, rawModuleCode);
					prototypeComponents.put(name, prototypeComponent);
					
				}
			}
			
		} catch (ParserConfigurationException e) {
			e.printStackTrace();
		} catch (SAXException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		System.out.println("prototypeComponents: " + prototypeComponents);
		
	}
	
	
}
